package net.funtip.jdbc;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;

import net.funtip.jdbc.entity.ParameterSet;
import net.funtip.jdbc.entity.QueryStatistic;
import net.funtip.jdbc.entity.QueryStatisticImpl;

public class JDBCLogger {
	
	protected Logger log = Logger.getLogger(this.getClass().getName());

    protected static Map<QueryStatistic,QueryStatistic> queryStats =  Collections.synchronizedMap(new TreeMap<QueryStatistic,QueryStatistic>());
    protected static Hashtable<String,QueryStatistic> queries = new Hashtable<String,QueryStatistic>();

    private static ThreadLocal<String> statement = new ThreadLocal<String>();
    private static ThreadLocal<Long> startTime = new ThreadLocal<Long>();

    private static JDBCLogger instance = null;
    
	private static Map<String, StatementPrepareCloseCount> prepareCloseCounts = new HashMap<String, StatementPrepareCloseCount>();
	
	public static Map<String, StatementPrepareCloseCount> getPrepareCloseCounts() {
		return prepareCloseCounts;
	}
	
	public static class StatementPrepareCloseCount {
		private final String query;
		private int prepareCount;
		private int closeCount;
		
		public StatementPrepareCloseCount(String query) {
			this.query = query;
		}
		
		public int getPrepareCount() {
			return prepareCount;
		}
		
		public void setPrepareCount(int prepareCount) {
			this.prepareCount = prepareCount;
		}
		
		public int getCloseCount() {
			return closeCount;
		}
		
		public void setCloseCount(int closeCount) {
			this.closeCount = closeCount;
		}

		public String getQuery() {
			return query;
		}
		
	}

    protected JDBCLogger() {
    }
    
    public static JDBCLogger getInstance() {
    	if (instance == null) {
    		instance = new JDBCLogger();
    	}
    	return instance;
    }
    
    public synchronized void startLogSqlQuery(String sql) {
    	String sqlString = statement.get();
    	if (sqlString != null) {
            log.warning("WARNING: overwriting sql query log time for " + sql);
    	}
    	statement.set(sql);
    	startTime.set(System.currentTimeMillis());
    }

    public synchronized void endLogSqlQuery(String sql) {
    	String sqlString = statement.get();
    	if (sql == null || !sql.equals(sqlString)) {
    		log.info("SQL statement mismatch! Started: "+sqlString+", ended: "+sql);
    	}
    	if (sqlString == null) {
    		log.info("ERROR: in endLogSqlQuery no entry!");
    	}
    	long time = System.currentTimeMillis() - startTime.get();
    	statement.remove();
    	
        logSqlQuery(sql, time);
    }

	public synchronized void logSqlQuery(String sql, long time) {
		try {
            String name = getNameBySQL(sql);
            QueryStatistic qs = (QueryStatistic) queries.get(name);
            if (qs == null) {
                qs = new QueryStatisticImpl(name);
            } else {
                queryStats.remove(qs);
            }
            qs.addExecution(time, 0);
            queryStats.put(qs, qs);
            queries.put(name, qs);

        } catch (Throwable e) {
	        log.info(e.getMessage());
        }
	}

    public synchronized void startLogSqlNext(String sql) {
    	String sqlString = statement.get();
    	if (sqlString != null) {
            log.info("WARNING: overwriting sql query log time for " + sql);
    	}
    	statement.set(sql);
    	startTime.set(System.currentTimeMillis());
    }

    public synchronized void endLogSqlNext(String sql) {
    	String sqlString = statement.get();
    	if (sql == null || !sql.equals(sqlString)) {
    		log.warning("SQL statement mismatch! Started: "+sqlString+", ended: "+sql);
    	}
    	if (sqlString == null) {
    		log.warning("ERROR: in endLogSqlQuery no entry!");
    	}
    	long time = System.currentTimeMillis() - startTime.get();
    	statement.remove();

    	try {
            String name = getNameBySQL(sql);
            QueryStatistic qs = (QueryStatistic) queries.get(name);
            if (qs == null) {
                qs = new QueryStatisticImpl(name);
            } else {
                queryStats.remove(qs);
            }
            qs.addNext(time);

            queryStats.put(qs, qs);
            queries.put(name, qs);

        } catch (Throwable e) {
	        log.warning(e.getMessage());
        }
    }

    public String getNameBySQL(String sql) {
        String s = sql.toUpperCase().replace('\n', ' ');
        return s;
    }

	public synchronized Collection<QueryStatistic> getStatements() {
    	Collection<QueryStatistic> l = new ArrayList<QueryStatistic>();
    	for (QueryStatistic qs: (Collection<QueryStatistic>) queryStats.values()) {
    		l.add(qs);
    	}
    	return recalculatePercentages(l);
    }
    
    protected long calculateTotalTime(Collection<QueryStatistic> list) {
    	long totalTime = 0;
    	for (QueryStatistic qs: list) {
    		totalTime += qs.getTotalTime();
    	}
    	return totalTime;
    }

	public synchronized Collection<QueryStatistic> recalculatePercentages(Collection<QueryStatistic> queries) {
		double overallTime = calculateTotalTime(queries);
		
		// update the percentage each statements is using
		for (QueryStatistic qs: queries) {
			qs.setPercentage((int) Math.round(qs.getTotalTime()*10000/overallTime));
		}
		return queries;
	}


    public synchronized void flush() {
        queryStats.clear();
        queries.clear();
    }

	public void logParameter(String sql, ParameterSet params) {
    	String sqlString = statement.get();
    	if (sql == null || !sql.equals(sqlString)) {
    		log.warning("SQL statement mismatch! Started: "+sqlString+", ended: "+sql);
    	}
    	if (sqlString == null) {
    		log.warning("ERROR: in endLogSqlQuery no entry!");
    	}

    	try {
            String name = getNameBySQL(sql);
            QueryStatistic qs = (QueryStatistic) queries.get(name);
            if (qs == null) {
                qs = new QueryStatisticImpl(name);
                queryStats.put(qs, qs);
                queries.put(name, qs);
            }
            qs.addParametersForExecution(params);

        } catch (Throwable e) {
	        log.warning(e.getMessage());
        }
	}

	public void closeStatement() {
		statement.remove();
	}

	public void incrementPrepareCount(String query) {
		synchronized (prepareCloseCounts) {
			StatementPrepareCloseCount spc = prepareCloseCounts.get(query);
			if (spc == null) {
				spc = new StatementPrepareCloseCount(query);
				prepareCloseCounts.put(query, spc);
			}
			spc.setPrepareCount(spc.getPrepareCount() + 1);
		}
	}

	public void incrementCloseCount(String query) {
		synchronized (prepareCloseCounts) {
			StatementPrepareCloseCount spc = prepareCloseCounts.get(query);
			if (spc == null) {
				spc = new StatementPrepareCloseCount(query);
				prepareCloseCounts.put(query, spc);
			}
			spc.setCloseCount(spc.getCloseCount() + 1);
		}
	}
}
